/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package MortgageCore;
import Household.Household;
import Bank.Bank;
import simframe.simulation.*;

import java.util.*;

/**
 *
 * @author karakots
 */
public class MortgageMarket extends SimObject implements iDebtMarket, iGoverned {

    // SSN
    // Turned this into a singleton class

    private static final MortgageMarket mrkt = new MortgageMarket();

    public static MortgageMarket Get() {
        return mrkt;
    }
    // a dictionary with keys of offer type and values that are lists of offers
    // There are two elements in the list of offers:
    //  Sell: sellers are banks, and offer mortgages to households
    //  Buy: buyers are households, who use offers to specify the mortgage they want
    protected Map<MortgageOffer.Type, Map<iBuyerSeller, List<MortgageOffer>>> offers =
            new HashMap<MortgageOffer.Type, Map<iBuyerSeller, List<MortgageOffer>>>();
    private int numMatched = 0;
    private int numNoMatch = 0;

    private MortgageMarket() {
        // nothing to init yet
    }

    public static final Swarm.SwarmAction<MortgageMarket> ageOffers = new Swarm.SwarmAction<MortgageMarket>() {
        public synchronized void exec(MortgageMarket mm) {
            mm.ageOffers();
        }
    };

  
    public static final Swarm.SwarmAction<MortgageMarket> tryToFillRequests = new Swarm.SwarmAction<MortgageMarket>() {
        public synchronized void exec(MortgageMarket mm) {
            mm.tryToFillRequests();
        }
    };

    // SimObject required methods
    public void reset() {
    }

    public boolean dead() {
        return false;
    }

    // this assumes the household has only one mortgage request out at a time
    public synchronized void tryToFillRequests() {
        Map<iBuyerSeller, List<MortgageOffer>> hhRequestList = offers.get(MortgageOffer.Type.Bid);
        if (hhRequestList == null || hhRequestList.size() == 0 )
             return;

        //MortgageOffer hhMortgageRequest = null;
        // for each hh requesting a mortgage
        int activeRequests = 0;
        for (List<MortgageOffer> hhBidList : hhRequestList.values()) {
            MortgageOffer bestOffer = null;
            MortgageOffer mortgageRequest = null;
            if (hhBidList != null && !hhBidList.isEmpty()) {
                for (MortgageOffer hhMortgageRequest : hhBidList) {
                    activeRequests++;
                    // find the best offer
                    bestOffer = getBestOffer(hhMortgageRequest);
                    if (bestOffer == null)
                        numNoMatch++;
                    else
                        numMatched++;
                    mortgageRequest = hhMortgageRequest;
                }  // next mortgage request (hh should only have one
             }  // end check for empty list

            // if there is a best offer
            if ((mortgageRequest != null) && (bestOffer != null)) {
                inactivateBestOffer(bestOffer, mortgageRequest);
                Bank bestOfferBank = (Bank) bestOffer.getAgent();
                // give the loan the buyers min amount to borrow
                bestOffer.setAmountToBorrow(mortgageRequest.getAmountToBorrrow());
                // the the requesting hgousehold about the offer
                iBuyerSeller borrower = mortgageRequest.getAgent();
                // tell the bank, who will generate a nortgage and assign it to the house
                bestOfferBank.action(bestOfferBank.preApproveMortgage(bestOffer, borrower));
            }
        } // next hh
        int debug = activeRequests;
    }

    private synchronized MortgageOffer getBestOffer(MortgageOffer hhMortgageRequest) {
        Map<iBuyerSeller, List<MortgageOffer>> bankList = offers.get(MortgageOffer.Type.Ask);
        if (bankList == null || bankList.size() == 0 )
             return null;
        
        MortgageOffer bestOffer = null;
        Household borrower = (Household) hhMortgageRequest.getAgent();
        double borrowerAssets = borrower.getTotalCashAssets();
        double borrowerMonthlyIncome = borrower.getMonthlySalary();
        double borrowerAmtDesired = hhMortgageRequest.getAmountToBorrrow();
        // for each bank offering mortgages
        for (List<MortgageOffer> bankAskList : bankList.values()) {
            if (bankAskList != null && !bankAskList.isEmpty()) {
                Bank lender = (Bank) bankAskList.get(0).getAgent();
                if (lender.servesBorrowerState(borrower)) {
                    double bestOfferUtility = -1;
                    double offerUtility;
                    boolean gotAnApp = false;
                    boolean borrowerQualified = false;
                    for (MortgageOffer bankMortgageOffer : bankAskList) {
                        // get the next offer
                        if ((bankMortgageOffer.getIsActiveOffer()) && (bankMortgageOffer.getDaysOfferValid() > 0)) {
                            // if HH meets mortgage requirements
                            gotAnApp = true;
                            if (lender.rateBorrowerForMortgage(hhMortgageRequest, bankMortgageOffer,
                                    borrowerAssets, borrowerMonthlyIncome, borrower, borrowerAmtDesired)) {
                                borrowerQualified = true;
                                // score mortgage for HH
                                offerUtility = borrower.hb().calcMortgageUtility(bankMortgageOffer.getMortgageTerms());
                                // if HH score is higher than previous high
                                if (offerUtility > bestOfferUtility) {
                                    // save this as best offer
                                    bestOfferUtility = offerUtility;
                                    bestOffer = bankMortgageOffer;
                                    break;
                                } // end check for best utility
                                // all loans from a single bank have the same utility...
                                // except that one bank can offer both 15 and 30 year loans
                                else
                                    break;
                            }  // end check for valid credit score
                            // all loans from a single bank have the same utility...
                            // except that one bank can offer both 15 and 30 year loans
                            else
                                break;
                        } // end check for active offer
                    }  // next mortgage offer
                    if (gotAnApp) {
                        lender.getLendingPolicy().newApplication();
                        if (borrowerQualified) {
                            lender.getLendingPolicy().appQualified();
                        }
                    }
                } // end check for state
            }  // end check for empty list
        } // next bank


        // if there is a best offer
        if (bestOffer != null) {
            //inactivateBestOffer(bestOffer, hhMortgageRequest, borrowerAmtDesired);
        }
        return bestOffer;
    }

    private void inactivateBestOffer(MortgageOffer bestOffer, MortgageOffer hhMortgageRequest) {
        // deactivate offer so no one else can choose it
            bestOffer.setIsActiveOffer(false);
            bestOffer.setIsAccepted(true);
        bestOffer.setAmountToBorrow(hhMortgageRequest.getAmountToBorrrow());
        bestOffer.getAgent().offerAccepted(bestOffer);
        // also set the hh request to expire, as it is now filled
            hhMortgageRequest.setIsActiveOffer(false);
            hhMortgageRequest.setIsAccepted(true);
        hhMortgageRequest.getAgent().offerAccepted(hhMortgageRequest);
    }

    public synchronized void ageOffers() {
        // for each offer type
        ageOffersofType(MortgageOffer.Type.Bid);
        ageOffersofType(MortgageOffer.Type.Ask);
    }
    
    private void ageOffersofType(MortgageOffer.Type mt)
    {
        Map<iBuyerSeller, List<MortgageOffer>> agentOfferList = offers.get(mt);

         if (agentOfferList == null || agentOfferList.size() == 0 )
             return;

        // go through all the agents' offer lists
        for (List<MortgageOffer> offerList : agentOfferList.values()) {
            if (offerList != null && !offerList.isEmpty()) {
                // go through the offer list for each agent
                ListIterator<MortgageOffer> iter = offerList.listIterator();
                while(iter.hasNext()) {
                    MortgageOffer offer = iter.next();

                    if (offer.getIsAccepted()) {
                        //iBuyerSeller agent = offer.getAgent();
                        iter.remove();
                        //agent.offerAccepted(offer);
                    } else if (offerExpired(offer)) {
                        iBuyerSeller agent = offer.getAgent();
                        iter.remove();
                        agent.offerExpired(offer);
                    }
                }
            }
        }
    }

    private boolean offerExpired(MortgageOffer offerToAge) {
        int daysLeft = offerToAge.getDaysOfferValid();
        boolean hitZero = false;
        if (daysLeft > 0) {
            if (daysLeft == 1)
                hitZero = true;
            daysLeft -= 1;
            offerToAge.setDaysOfferValid(daysLeft);
        }
        if (hitZero)
            return true;
        else
            return false;
    }

    //
    // iDebtMarket
    //
	public synchronized void addOffer(MortgageOffer offer) {

        iBuyerSeller agent = offer.getAgent();
        
        // locally we create a pointer to access either the borrower or the seller map,
        // depending on the type of offer
        Map<iBuyerSeller, List<MortgageOffer>> agentOfferList = offers.get(offer.getType());

        // if we have not yet allocated any storage for the buy list or sell list, we do this now
        if (agentOfferList == null) {
            agentOfferList = new HashMap<iBuyerSeller, List<MortgageOffer>>();
            offers.put(offer.getType(), agentOfferList);
        }

        // set the list we are going to look at to be the offer list for the specified agent
        // banks can provide multiple offers, housholds will just have one offer, but
        // we do them both the same to preserve generality
        List<MortgageOffer> offerList = agentOfferList.get(agent);

        // once again, if we have no storage, we create it
        if (offerList == null) {
            offerList = new LinkedList<MortgageOffer>();
            agentOfferList.put(agent, offerList);
        }

        // finally, we can ad the offer to the list
        offerList.add(offer);
	}

    public Bank getBestRateBank() {
        Bank bestRateBank = null;
        double bestRate = -1;

        // for each bank
        Map<iBuyerSeller, List<MortgageOffer>> bankList = offers.get(MortgageOffer.Type.Ask);
        if (bankList == null || bankList.size() == 0 )
             return null;

        // for each bank offering mortgages
        for (List<MortgageOffer> bankAskList : bankList.values()) {
            if (bankAskList != null && !bankAskList.isEmpty()) {
                Bank lender = (Bank) bankAskList.get(0).getAgent();
                double rate = lender.getLendingPolicy().monthlyMortgageInterestRate;
                if ((bestRate == -1) || (rate < bestRate)) {
                    bestRate = rate;
                    bestRateBank = lender;
                }
            }  // end check for empty list
        } // next bank
        return bestRateBank;
    }

    // iGoverned Interface
    public void updateRegsAndExternals(Regulations regs, Externals ext) {

    }
}
